import base64
import json
import os
import re
import shutil
from hashlib import sha1
from flask import Flask, request, jsonify, send_file

from src.api.qrcode_generator import QRCodeGenerator
from src.core.get_hash import calculate_file_hash
from src.lib.config import file_download_path, qr_download_path, hash_to_file_path, HOST, PORT, MAX_UPLOAD_SIZE, \
    MAX_DOWNLOAD_SIZE
from src.lib.exception import FileSizeExceedError

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = MAX_UPLOAD_SIZE


# 确保目录存在
os.makedirs(file_download_path, exist_ok=True)
os.makedirs(qr_download_path, exist_ok=True)


# 错误处理装饰器
from functools import wraps
from werkzeug.exceptions import RequestEntityTooLarge

# 定义错误处理装饰器
# 该装饰器用于捕获视图函数中的异常，并返回统一的错误响应
def handle_errors(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        try:
            result = f(*args, **kwargs)
            if result is None:
                return jsonify({"data": None, "error": None}), 200
            return result
        except RequestEntityTooLarge:
            return jsonify({"data": None, "error": f"文件大小超过{MAX_UPLOAD_SIZE//(1024*1024)}MB限制"}), 400
        except FileSizeExceedError as e:
            return jsonify({"data": None, "error": f"文件大小超过{MAX_DOWNLOAD_SIZE//(1024*1024)}MB下载限制"}), 413
        except FileNotFoundError as e:
            return jsonify({"data": None, "error": f"文件路径不存在: {str(e)}"}), 404
        except PermissionError as e:
            return jsonify({"data": None, "error": f"权限错误: {str(e)}"}), 403
        except Exception as e:
            app.logger.error(f'Server error: {str(e)}')
            return jsonify({"data": None, "error": "服务器内部错误"}), 500
    return wrapper

@app.route('/')
@handle_errors
def index():
    return jsonify({"data": None, "error": "Can not connect to this page."}), 404

@app.route("/file/post", methods=['POST'])
@handle_errors
def file_post():
    # 检查请求中是否包含文件
    if 'file' not in request.files:
        return jsonify({"data": None, "error": "No file part in the request."}), 400
    file = request.files['file']
    # 检查文件是否有文件名
    if file.filename == '':
        return jsonify({"data": None, "error": "No selected file."}), 400
    # 检查文件名是否合法
    if not re.match(r'^[a-zA-Z0-9_.-]+$', file.filename):
        return jsonify({"data": None, "error": "Invalid file name."}), 400

    # 保存文件
    # 获取数据流
    chunk = b''
    while True:
        chunk += file.stream.read(8192)
        if not chunk:
            break

    # 计算文件哈希值
    file_hash = sha1(chunk).hexdigest()

    file_path = os.path.join(file_download_path, file_hash + '.' + file.filename.split(".")[-1] + '.QRFile')
    print(f"保存文件路径: {file_path}")  # 调试信息

    with open(file_path, 'wb') as f:
        f.write(chunk)

    # 存储哈希值和文件路径的映射关系
    hash_to_file_path[file_hash] = file_path

    # 生成二维码
    qr = QRCodeGenerator()
    qr_bytes = qr.generate(data=f"http://{HOST}:{PORT}/file/get/" + file_hash)

    # 将二维码字节数据进行 Base64 编码
    qr_base64 = base64.b64encode(qr_bytes).decode('utf-8')

    # 下载二维码
    qr_path = os.path.join(qr_download_path, f"{file_hash}.png")
    print(f"保存二维码路径: {qr_path}")  # 调试信息
    qr.download(qr_path)

    return jsonify({"data": "File uploaded successfully.", "error": None, "qr": qr_base64, "hash": file_hash}), 200


@app.route("/file/get/<file_hash>", methods=['GET'])
def file_get(file_hash):
    try:
        # 从映射关系中查找文件路径
        file_path = hash_to_file_path.get(file_hash)
        if not file_path:
            return jsonify({"data": None, "error": "未找到匹配哈希值的文件。"}), 404
        # 检查文件路径是否存在于映射关系中
        if file_path not in hash_to_file_path.values():
            return jsonify({"data": None, "error": "文件路径未在映射关系中。"}), 404
        print(f"获取文件路径: {file_path}")
        # 检查文件是否存在
        if not os.path.exists(file_path):
            return jsonify({"data": None, "error": f"文件 {file_path} 不存在。"}), 404
        # 检查文件大小
        file_size = os.path.getsize(file_path)
        if file_size > MAX_DOWNLOAD_SIZE:
            raise FileSizeExceedError(f"文件大小 {file_size} 字节超过限制")
        # 获取文件名
        filename = os.path.basename(file_path)
        return send_file(file_path, as_attachment=True, attachment_filename=filename), 200
        # return jsonify({"data": None, "error": "未找到匹配哈希值的文件。"}), 404
    except Exception as e:
        return jsonify({"data": None, "error": str(e)}), 500


# 分块上传相关配置
from src.lib.config import CHUNK_SIZE, TEMP_UPLOAD_DIR, MAX_CONCURRENT_UPLOADS
import glob
from filelock import FileLock

# 初始化分块上传临时目录
os.makedirs(TEMP_UPLOAD_DIR, exist_ok=True)

# 分块上传相关函数
# 这些函数用于处理文件的分块上传、合并和状态查询
# 初始化分块上传
@app.route('/file/upload', methods=['POST'])
@handle_errors
# 初始化分块上传会话，返回上传 ID 和分块大小等信息
def init_chunk_upload():
    try:
        file_hash = request.json.get('file_hash')
        chunk_dir = os.path.join(TEMP_UPLOAD_DIR, file_hash)
        os.makedirs(chunk_dir, exist_ok=True)

        # 删除旧的upload_id相关代码
        with open(os.path.join(chunk_dir, 'metadata.json'), 'w') as f:
            json.dump({
                'file_name': request.json.get('file_name'),
                'total_chunks': request.json.get('total_chunks'),
                'file_hash': file_hash
            }, f)

        return jsonify({
            "data": {"upload_id": file_hash, "chunk_size": CHUNK_SIZE, "max_concurrent_uploads": MAX_CONCURRENT_UPLOADS},
            "error": None
        }), 200
    except Exception as e:
        return jsonify({"data": None, "error": str(e)}), 500

# 上传分块
@app.route('/file/chunk_upload/<file_hash>', methods=['POST'])
@handle_errors
# 上传文件的一个分块，并进行哈希校验
def upload_chunk(file_hash):
    try:
        chunk_number = int(request.form.get('chunk_number'))
        chunk_file = request.files['chunk']
        chunk_hash = request.form.get('chunk_hash')

        chunk_dir = os.path.join(TEMP_UPLOAD_DIR, file_hash)
        chunk_path = os.path.join(chunk_dir, f'chunk_{chunk_number:04d}')

        # 保存分块文件
        with open(chunk_path, 'wb') as f:
            while True:
                chunk = chunk_file.stream.read(CHUNK_SIZE)
                if not chunk:
                    break
                f.write(chunk)
            f.flush()
            os.fsync(f.fileno())

        # 校验分块哈希
        if calculate_file_hash(chunk_path, 'sha1') != chunk_hash:
            os.remove(chunk_path)
            return jsonify({"data": None, "error": {"code": 400, "message": "分块哈希校验失败"}}), 400

        return jsonify({"data": "分块上传成功", "error": None}), 200
    except Exception as e:
        return jsonify({"data": None, "error": str(e)}), 500

# 合并分块
@app.route('/file/complete_upload/<file_hash>', methods=['POST'])
@handle_errors
# 合并所有分块文件，并进行最终的哈希校验
def merge_chunks(file_hash):
    try:
        chunk_dir = os.path.join(TEMP_UPLOAD_DIR, file_hash)
        lock_file = os.path.join(chunk_dir, '.lock')

        with FileLock(lock_file):
            metadata_path = os.path.join(chunk_dir, 'metadata.json')
            with open(metadata_path) as f:
                metadata = json.load(f)

            # 安全路径处理
            final_file_name = os.path.basename(metadata['file_name'])
            final_path = os.path.abspath(os.path.join(file_download_path, metadata['file_hash'] + '.QRFile'))

            if not final_path.startswith(os.path.abspath(file_download_path)):
                raise ValueError("非法文件路径")

            # 获取所有分块并按序号排序
            chunks = sorted(glob.glob(os.path.join(chunk_dir, 'chunk_*')))

            if len(chunks) != metadata['total_chunks']:
                return jsonify({"data": None, "error": "分块数量不匹配"}), 400

            final_path = os.path.join(file_download_path, metadata['file_hash'] + '.QRFile')
            with open(final_path, 'wb') as output:
                for chunk in chunks:
                    with open(chunk, 'rb') as f:
                        output.write(f.read())
                    os.remove(chunk)

            # 最终文件哈希校验
            if calculate_file_hash(final_path, 'sha1') != metadata['file_hash']:
                os.remove(final_path)
                return jsonify({"data": None, "error": {"code": 400, "message": "最终文件哈希校验失败"}}), 400

            # 清理临时目录
            shutil.rmtree(chunk_dir, ignore_errors=True)

            # 保存哈希映射
            hash_to_file_path[metadata['file_hash']] = final_path

            return jsonify({"data": "文件合并成功", "error": None}), 200
    except Exception as e:
        # 可以在这里添加日志记录，方便调试
        app.logger.error(f"合并分块时发生错误: {str(e)}")
        return jsonify({"data": None, "error": str(e)}), 500

# 获取上传状态
@app.route('/file/upload/status/<file_hash>', methods=['GET'])
@handle_errors
# 获取文件上传的状态，包括已上传的分块数量和是否已合并
def get_upload_status(file_hash):
    chunk_dir = os.path.join(TEMP_UPLOAD_DIR, file_hash)
    if not os.path.exists(chunk_dir):
        return jsonify({"data": None, "error": "上传会话不存在"}), 404

    chunks = glob.glob(os.path.join(chunk_dir, 'chunk_*'))
    metadata_path = os.path.join(chunk_dir, 'metadata.json')

    status = {
        'uploaded_chunks': len(chunks),
        'is_merged': os.path.exists(os.path.join(chunk_dir, '.merged'))
    }

    if os.path.exists(metadata_path):
        with open(metadata_path) as f:
            metadata = json.load(f)
        status['total_chunks'] = metadata['total_chunks']
        status['file_name'] = metadata['file_name']

    return jsonify({"data": status, "error": None})


if __name__ == '__main__':
    app.run(debug=True)